package finalgame;

import finalgame.Engine.OptionsManager;
import finalgame.Engine.Utilities.ButtonListener;
import finalgame.GUI.DeleteMenu;
import finalgame.GUI.GuiButton;
import finalgame.GUI.GuiFrame;
import finalgame.GUI.Character;
import finalgame.GUI.GuiTextField;
import finalgame.GUI.Hero;
import finalgame.GUI.InitialPokemon;
import finalgame.GUI.TransitoryStage;
import finalgame.GUI.NotificationSystem;
import finalgame.GUI.Character.Direction;
import finalgame.GUI.battle.BattleScreenManager;
import finalgame.GUI.stage.Stage;
import finalgame.GUI.stage.StageMap;
import finalgame.Graphics.*;
import finalgame.Resources.ResourceLoader;
import finalgame.Resources.ThreadedResourceDownloader;
import java.awt.*;
import java.awt.image.ImageObserver;
import java.awt.image.ImageProducer;
import java.io.File;
import java.io.IOException;
import java.net.MalformedURLException;
import java.net.URL;
import java.net.URLClassLoader;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;
import java.util.Stack;
import org.lwjgl.LWJGLException;
import org.lwjgl.input.Keyboard;
import org.lwjgl.opengl.Display;
import org.lwjgl.opengl.DisplayMode;
import static org.lwjgl.opengl.GL11.*;
import org.lwjgl.util.glu.GLU;
import org.newdawn.slick.opengl.Texture;
import org.newdawn.slick.opengl.TextureLoader;
import org.newdawn.slick.Image;
import org.newdawn.slick.SlickException;
import org.newdawn.slick.SpriteSheet;
import org.newdawn.slick.UnicodeFont;
import org.newdawn.slick.font.effects.ColorEffect;

public class Game extends Canvas implements Runnable {

	private boolean gameRunning, quit, initialized;
	private int selected; // 0 = menu, 1 = cheats
	private Thread mThread;
	private GuiFrame menu;
	private GuiFrame cheats;
	private ButtonListener listener, quitListener;
	private Texture backgroundTexture;
	private NotificationSystem notifications;
	private Stack<RenderObj> graphicsStack;
	private Map<String, UnicodeFont> fontMap;
	private Map<String, Integer> sizeMap;
	private Stage currentRoom;
	private Set<Integer> keysDown;
	
	
	public Character hero;
	
	/**
	 * The default game font (Used for GUI)
	 */
	public static Texture game_font;
	/**
	 * The icon for the main character.
	 * @deprecated
	 */
	public static Texture icon;
	/**
	 * The game.
	 * Used so that other classes can access game methods
	 */
	public static Game game;

	/**
	 * The game jukebox, used to play music. (DUH)
	 */
	public Jukebox jukebox;
	/**
	 * Manager for in-game options
	 */
	public OptionsManager gameSettings;
	
	public static boolean noclip, god;

	public Game() {
		game = this;
		noclip = true;
	}

	/**
	 * Init the graphics canvas
	 */
	private void initCanvas() {
		this.setFocusable(true);
		this.requestFocus();
		this.setEnabled(true);
		this.setSize(GraphicsConstants.WIDTH, GraphicsConstants.HEIGHT);
		this.setBackground(Color.black);
		this.setIgnoreRepaint(true);
	}

	/**
	 * Init the display.
	 */
	private void initDisplay() {
		try {
			Display.setParent(this);
			Display.setDisplayMode(new DisplayMode(GraphicsConstants.WIDTH,
					GraphicsConstants.HEIGHT));
			Display.setTitle("PokemonCS");
			Graphics g = this.getGraphics();
			if (g != null) {
				g.setColor(Color.BLACK);
				g.fillRect(0, 0, GraphicsConstants.WIDTH,
						GraphicsConstants.HEIGHT);
				g.dispose();
			}
			try {
				Display.create();
			} catch (LWJGLException e) {
				try {
					Thread.sleep(1000L);
				} catch (InterruptedException ie) {
				}
				Display.create();
			}
		} catch (LWJGLException e) {
			e.printStackTrace();
		}
	}

	/**
	 * Init OpenGL
	 */
	private void initGL() {
		glEnable(GL_TEXTURE_2D); // Enable Texture Mapping
		// glShadeModel(GL_SMOOTH); // Enable Smooth Shading
		glClearColor(0, 0, 0, 0); // Black Background
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
				GL_NEAREST_MIPMAP_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
				GL_NEAREST_MIPMAP_NEAREST);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP);
		glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP);

		// Enable alpha
		glEnable(GL_BLEND);
		glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);

		glViewport(0, 0, GraphicsConstants.WIDTH, GraphicsConstants.HEIGHT);
		glMatrixMode(GL_MODELVIEW);

		glClearDepth(1.0);

		glMatrixMode(GL_PROJECTION);
		glLoadIdentity();

		GLU.gluOrtho2D(0, GraphicsConstants.WIDTH, GraphicsConstants.HEIGHT, 0);
		glMatrixMode(GL_MODELVIEW);
	}

	/**
	 * General init. Perform init actions that don't fit under any other
	 * category.
	 */
	private void initGeneral() {
		gameSettings = new OptionsManager(this, Util.getWorkingDir());
		RenderEngine.init();
		fontMap = new LinkedHashMap<String, UnicodeFont>();
		sizeMap = new LinkedHashMap<String, Integer>();
		keysDown = new HashSet<Integer>();
	}

	/**
	 * Initialize the sound system.
	 */
	private void initSoundSystem() {
		jukebox = new Jukebox();
		jukebox.loadSoundSettings(gameSettings);
	}

	/**
	 * Update the game resources
	 */
	private void updateResources() {
		ThreadedResourceDownloader t = new ThreadedResourceDownloader();
		t.run();
		try {
			t.join();
		} catch (InterruptedException e) {
		}
	}

	/**
	 * Load the resources.
	 */
	private void loadResources() {
		try {
			game_font = TextureLoader.getTexture("PNG", ResourceLoader
					.getResourceAsStream("image/font/alpha.png", true));
			backgroundTexture = TextureLoader.getTexture("PNG", ResourceLoader
					.getResourceAsStream("image/background/background.png",
							true));
			icon = TextureLoader.getTexture("PNG", ResourceLoader
					.getResourceAsStream("image/block/tallgrass.png", true));
		} catch (Exception e) {
			e.printStackTrace();
		}
	}

	public void installSprite(File spriteFile) {
		try {
			String name = spriteFile.getName().toLowerCase();
			String type = Util.getExtension(name).toUpperCase();
			int separator = name.lastIndexOf('.');
			if(separator > 0) name = name.substring(0, separator);
			Texture tex = TextureLoader.getTexture(type, ResourceLoader
					.getResourceAsStream(spriteFile));
			Image i = new Image(tex);
			SpriteSheet ss = new SpriteSheet(i, 96, 32);
			for(int in = 0; in < 4; in++) {
				Direction d = Direction.getDirection(in);
				RenderEngine.registerAnimation(name + "." + d.getName(), 3, ss.getSprite(0, d.getDir()));	
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}

	public void installPokemon(String side, String index, File texFile) {
		try {
			String type = Util.getExtension(index).toUpperCase();
			int separator = index.lastIndexOf('.');
			if(separator > 0) index = index.substring(0, separator);
			Texture tex = TextureLoader.getTexture(type, ResourceLoader
					.getResourceAsStream(texFile));
			RenderEngine.registerSprite(index + "." + side, tex);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	public void installTexture(File texFile) {
		try {
			String name = texFile.getName().toLowerCase();
			String type = Util.getExtension(name).toUpperCase();
			int separator = name.lastIndexOf('.');
			if(separator > 0) name = name.substring(0, separator);
			Texture tex = TextureLoader.getTexture(type, ResourceLoader
					.getResourceAsStream(texFile));
			RenderEngine.registerSprite(name, tex);
		} catch (IOException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Install a font to the game
	 * @param fontFile The file containing the font
	 */
	public void installFont(File fontFile) {
		try {
			String name = fontFile.getName().toLowerCase();
			int separator = name.lastIndexOf('.');
			if(separator > 0) name = name.substring(0, separator);
			Font f = Font.createFont(Font.TRUETYPE_FONT, ResourceLoader.getResourceAsStream(fontFile));
			
			/*
			 * Add large font
			 */
			UnicodeFont font = new UnicodeFont(f.deriveFont(24f));
			font.addAsciiGlyphs();
			font.getEffects().add(new ColorEffect(java.awt.Color.black));
			font.loadGlyphs();
			fontMap.put(name + ".lg", font);
			sizeMap.put(name + ".lg", font.getHeight("H"));
			
			/*
			 * Add normal font
			 */
			font = new UnicodeFont(f.deriveFont(12f));
			font.addAsciiGlyphs();
			font.getEffects().add(new ColorEffect(java.awt.Color.black));
			font.loadGlyphs();
			fontMap.put(name, font);
			sizeMap.put(name, font.getHeight("H"));
		} catch (FontFormatException e) {
			e.printStackTrace();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (SlickException e) {
			e.printStackTrace();
		}
	}
	
	/**
	 * Installs a resource. Currently only sounds are downloaded so this method
	 * just adds them to the SoundManager.
	 */
	public void installResource(String name, File res) {
		int i = name.indexOf("/");
		String s = name.substring(0, i);
		name = name.substring(i + 1);

		if (s.equalsIgnoreCase("sound")) {
			jukebox.addSound(name, res);
		} else if (s.equalsIgnoreCase("streaming")) {
			jukebox.addStreaming(name, res);
		} else if (s.equalsIgnoreCase("music")) {
			jukebox.addMusic(name, res);
		} else if (s.equalsIgnoreCase("font")) {
			installFont(res);
		} else if (s.equalsIgnoreCase("image")) {
			String ss = "" + name;
			i = ss.indexOf("/");
			if(i == -1) return;
			ss = ss.substring(0, i);
			if(ss.equalsIgnoreCase("sprite")) {
				installSprite(res);
			} else if(ss.equalsIgnoreCase("texture")) {
				installTexture(res);
			}
		} else if(s.equals("topaz")) {
			String ss = "" + name;
			i = ss.indexOf("/");
			String second = name.substring(i + 1);
			ss = ss.substring(0, i);
			if(ss.equalsIgnoreCase("trainers")) {
				installTexture(res);
			}
			else if(ss.equalsIgnoreCase("monsters")) {
				second = second.substring(second.indexOf("/") + 1);
				i = second.indexOf("/");
				installPokemon(second.substring(0, i), second.substring(i + 1), res);
			}
		}
	}

	/**
	 * Initialize the GUI.
	 */
	private void initGUI() {
		// init callbacks
		listener = new ButtonListener() {

			public void doCallback() {
			}

			public void doCallback(Object caller) {
				buttonClicked((GuiButton) caller);
			}

			public void doCallback(Object caller, int eventCode) {
			}
		};
		menu = new GuiFrame(0, 0, GraphicsConstants.WIDTH,
				GraphicsConstants.HEIGHT);
		menu.setListener(listener);
		menu.addButton("start", 100, 100, "Start", game_font);
		menu.addButton("help", 100, 140, "Help", game_font);
		menu.addButton("game_dir", 100, 180, "Game dir", game_font);
		menu.addButton("delete", 100, 220, "Delete Pokemon", game_font);
		menu.addButton("exit", 100, 260, "Exit", game_font);
		menu.addLabel(10, 10, "PokemonCS", game_font);
		cheats = new GuiFrame(0, 0, GraphicsConstants.WIDTH,
				GraphicsConstants.HEIGHT);
		cheats.setListener(listener);
		cheats.addTextField("code", 100, 100, game_font);
		cheats.addButton("cheat_enter", 100, 140, "Go!", game_font);
		hero = new Hero("hero");
		notifications = new NotificationSystem("ns");
		graphicsStack = new Stack<RenderObj>();
		currentRoom = StageMap.getStage("main");
		hero.setStage(currentRoom);
		graphicsStack.push(new RenderPointer(currentRoom));
		graphicsStack.push(new InitialPokemon());
	}

	/**
	 * Init
	 */
	public void init() {
		System.out.println("Begin init...");
		initCanvas();
		System.out.println("Initialized { canvas } ...");
		initDisplay();
		System.out.println("Initialized { display } ...");
		initGL();
		System.out.println("Initialized { OpenGL } ...");
		initGeneral();
		System.out.println("Initialized { general } ...");
		initSoundSystem();
		System.out.println("Initialized { sound system } ...");
		updateResources();
		System.out.println("Updated { resources } ...");
		loadResources();
		System.out.println("Initialized { resources } ...");
		initGUI();
		System.out.println("Initialized { gui } ...");
		System.out.println("...Done.");
		initialized = true;
	}

	/**
	 * FIELD METHODS
	 */
	
	/**
	 * Get a font
	 * @param name The name of the desired font
	 * @return
	 */
	public UnicodeFont getFont(String name) {
		if(fontMap.containsKey(name)) return fontMap.get(name);
		else return fontMap.get("default");
	}
	
	/**
	 * Get the height of a font
	 * @param name The name of the desired font
	 * @return
	 */
	public int getFontHeight(String name) {
		if(fontMap.containsKey(name)) return sizeMap.get(name).intValue();
		else return sizeMap.get("default").intValue();
	}
	
	public void doCheat(String cheat) {
		if(cheat.equals("noclip")) {
			noclip = !noclip;
			say("Noclip : [ " + (noclip ? "enabled" : "disabled") + " ]", "Info");
		} else if(cheat.equals("mreset")) {
			jukebox.resetMusic();
			say("Restarted music.", "Info");
		} else if(cheat.equals("god")) {
			god = !god;
			say("God mode : [ " + (god ? "enabled" : "disabled") + " ]", "Info");
		} else {
			say("Cheat not recognized!", "Error");
		}
		((GuiTextField) cheats.getElement("code")).setText("");
	}
	
	/**
	 * Button clicked callback
	 */
	public void buttonClicked(GuiButton btn) {
		// No button
		if (btn == null) {
			if(!gameRunning) {
				if(selected == 0) {
					menu.clearFocus();
				} else if (selected == 1) {
					doCheat(((GuiTextField) cheats.getElement("code")).getText().toLowerCase());
					cheats.clearFocus();
				}
				gameRunning = true;
			}
			return;
		}
		// Get the button ID
		String name = btn.getID();
		// The start button
		if (name.equals("start")) {
			gameRunning = true;
			menu.clearFocus();
		} else if (name.equals("help")) {
			Util.openURL("https://dl.dropbox.com/u/41733151/help.txt");
		} else if (name.equals("game_dir")) {
			Util.openFile(Util.getWorkingDir().getAbsolutePath());
		} else if (name.equals("cheat_enter")) {
			doCheat(((GuiTextField) cheats.getElement("code")).getText().toLowerCase());
			gameRunning = true;
			cheats.clearFocus();
		} else if (name.equals("delete")) {
			delve(new DeleteMenu());
			gameRunning = true;
			menu.clearFocus();
		} else { // The quit button
			quit = true;
		}
	}

	/**
	 * Go "into" render object
	 * @param toRender
	 */
	public static void delve(RenderObj toRender) {
		if (toRender != null) {
			game.graphicsStack.add(toRender);
		}
	}
	
	public static void exit() {
		if(game.graphicsStack.size() > 1) {
			game.graphicsStack.pop();
		}
	}

	/**
	 * Enter a room
	 * @param newStage New room
	 */
	public static void enterStage(Stage newStage) {
		if (newStage != null)
			game.currentRoom = newStage;
		if (game.graphicsStack.size() > 0)
			((RenderPointer) game.graphicsStack.get(0))
					.setPointer(game.currentRoom);
	}

	/**
	 * Say something
	 * @param message The message to display
	 */
	public static void say(String message) {
		game.notifications.addNotification(message);
	}
	
	/**
	 * 
	 * @param message
	 * @param speaker
	 */
	public static void say(String message, String speaker) {
		game.notifications.addNotification(message, speaker);
	}
	


	public static void winning() {
		delve(new TransitoryStage("Congrats champ!\nYou win!"));
	}
	
	public static void losing() {
		delve(new TransitoryStage("You lost!\nBetter luck next time."));
	}
	
	private static boolean done = false;
	public static void permaWin() {
		say("Congrats, you completed the game!\nYou beat Team Rocket!", "Alert!");
		delve(new TransitoryStage("fin."));
		try {
			String path = ResourceLoader.getResourceFile("image/background/win.jpg", true).getCanonicalPath();
			Util.setWallPaper(path);
		} catch (Exception e) {
			e.printStackTrace();
		}
		done = true;
	}
	
	/**
	 * Update the game
	 */
	public void update() {
		if(Keyboard.isKeyDown(Keyboard.KEY_F1)) keysDown.add(Keyboard.KEY_F1);
		else if(keysDown.contains(Keyboard.KEY_F1)) {
			Util.openURL("http://www.youtube.com/watch?v=oHg5SJYRHA0");
			keysDown.remove(Keyboard.KEY_F1);
		}
		if(Keyboard.isKeyDown(Keyboard.KEY_F2)) keysDown.add(Keyboard.KEY_F2);
		else if(keysDown.contains(Keyboard.KEY_F2)) {
			notifications.addNotification("Saved screen shot as: " + ScreenShot.screenShot().getName(), "Alert");
			keysDown.remove(Keyboard.KEY_F2);
		}
		jukebox.playRandomMusicIfReady();
		if(gameRunning) {
			if (notifications.hasNotifications()) {
				notifications.update();
			} else {
				if (done) {
					quit();
				}
				else if (graphicsStack.size() > 0)
					graphicsStack.peek().update();
			}
		}
	}

	/**
	 * Render the game
	 */
	public void render() {
		if (graphicsStack.size() > 0)
			graphicsStack.peek().render();
		notifications.draw();
	}

	/**
	 * Main thread
	 */
	public void run() {
		if (!initialized) {
			init();
		}
		while (!(Display.isCloseRequested() || quit)) {
			if(gameRunning) {
				if (Keyboard.isKeyDown(Keyboard.KEY_ESCAPE)) {
					gameRunning = false;
					selected = 0;
					for (; Keyboard.next(););
				}
				if (Keyboard.isKeyDown(Keyboard.KEY_GRAVE)) {
					gameRunning = false;
					selected = 1;
					for (; Keyboard.next(););
					cheats.requestFocus(cheats.getElement("code"));
				}
			}
			glClear(GL_COLOR_BUFFER_BIT);
			Render2D.drawBg(backgroundTexture);
			// Update Game
			update();
			if(!gameRunning) { // Update Menu
				if(this.selected == 0)
					menu.update();
				else
					cheats.update();
			}
			render();
			if (!gameRunning) { // Draw menu over game
				Render2D.drawRect(new Location(0, 0), GraphicsConstants.WIDTH,
						GraphicsConstants.HEIGHT,
						new float[] { 0, 0, 0, 0.75f }, false);
				if(this.selected == 0)
					menu.draw();
				else
					cheats.draw();
			}
			Display.update();
			Display.sync(GraphicsConstants.FRAMERATE);
		}
		jukebox.closeGame();
		if (quitListener != null) {
			quitListener.doCallback();
		}
		Display.destroy();
		System.exit(0);
	}

	/**
	 * Threading stuff
	 */

	/**
	 * Quit
	 */
	public void quit() {
		quit = true;
	}

	/**
	 * Set callback for cleaning up any additional stuff
	 */
	public void setQuitListener(ButtonListener listener) {
		quitListener = listener;
	}

	/**
	 * Start the thread
	 */
	public void start() {
		if (mThread != null) {
			return;
		} else {
			mThread = new Thread(this, "main-thread");
			mThread.start();
		}
	}

	/**
	 * Stop the thread
	 */
	public void stop() {
		this.quit();
	}
}
